/*!
 * Copyright (c) 2008. All rights reserved.
 * creator: lifesinger@gmail.com
 */

Unicorn = { version: '0.4.5' };

Unicorn.namespace = function() {
    var o = null, i, j, parts;
    for (i = 0; i < arguments.length; ++i) {
        parts = arguments[i].split('.');
        o = Unicorn;

        // Unicorn is implied, so it is ignored if it is included
        for (j = (parts[0] == 'Unicorn') ? 1 : 0; j < parts.length; ++j) {
            o[parts[j]] = o[parts[j]] || {};
            o = o[parts[j]];
        }
    }
    return o;
};

Unicorn.namespace('env', 'lang', 'util', 'widget', 'example');
YAHOO.lang.augmentObject(Unicorn.env, YAHOO.env);
YAHOO.lang.augmentObject(Unicorn.lang, YAHOO.lang);
YAHOO.lang.augmentObject(Unicorn.util, YAHOO.util);
YAHOO.lang.augmentObject(Unicorn.widget, YAHOO.widget);
Unicorn.log = YAHOO.log; // for debug
/*
 * languange utilites and extensions
 * ref:
 *  - Ext/Ext.js 2.1
 *  - http://developer-test.mozilla.org/docs/Core_JavaScript_1.5_Reference
 *  - prototype-1.6.0.2.js
 *
 * creator: lifesinger@gmail.com
 */

/**
 * Copies all the properties of config to obj.
 * @param {Object} obj The receiver of the properties
 * @param {Object} config The source of the properties
 * @param {Object} defaults A different object that will also be applied for default values
 * @return {Object} returns obj
 */
Unicorn.lang.apply = function(o, c, defaults) {
    if(defaults) {
        // no "this" reference for friendly out of scope calls
        Unicorn.apply(o, defaults);
    }
    if(o && c && typeof c == 'object') {
        for(var p in c) {
            o[p] = c[p];
        }
    }
    return o;
};

Unicorn.lang.apply(Unicorn.lang, {
	/**
	 * Copies all the properties of config to obj if they don't already exist.
	 * @param {Object} obj The receiver of the properties
	 * @param {Object} config The source of the properties
	 * @return {Object} returns obj
	 */
	applyIf: function(o, c) {
	    if(o && c) {
	        for(var p in c) {
	            if(typeof o[p] == 'undefined') {
	            	o[p] = c[p];
	            }
	        }
	    }
	    return o;
	},
	
	/**
	 * extend like Ext
	 */
	extend: function(subclass, superclass, overrides) {
		if(arguments.length == 2 && typeof overrides == 'object') {
			subclass = function() { };
		}
		
		YAHOO.lang.extend(subclass, superclass, overrides);
		return subclass;
	}
});

// shorthands
Unicorn.extend = Unicorn.lang.extend;

/**
 * @class String
 */
Unicorn.lang.applyIf(String.prototype, {
	/**
	 * Trims whitespace from either end of a string, leaving spaces within the string intact.
	 *<code>
	 | var s = '  foo bar  ';
	 | s.trim();	// -> 'foo bar'
	 *</code>
	 * @return {String} The trimmed string
	 */
	trim: function() {
	    return this.replace(/^\s+|\s+$/g, '');
	},
	
	/**
	 * Strips a string of any HTML tag.
	 * <code>
	 | var s = 'a <a href="#">link</a>';
	 | s.stripTags();	// -> 'a link'
	 * </code>
	 * @return {String} The stripped string
	 */
	stripTags: function() {
		return this.replace(/<.*?>/g, '');
	}
});

/**
 * @class Array
 */
Unicorn.lang.applyIf(Array.prototype, {
    /**
     * Checks whether or not the specified object exists in the array.
     * @param {Object} o The object to check for
     * @return {Number} The index of o in the array (or -1 if it is not found)
     */
    indexOf: function (o, fromIndex) {
		if (fromIndex == null) {
			fromIndex = 0;
		} else if (fromIndex < 0) {
			fromIndex = Math.max(0, this.length + fromIndex);
		}
		
		for (var i = fromIndex, len = this.length; i < len; ++i) {
			if (this[i] === o)
				return i;
		}
		return -1;
    },
    
    /**
     * Ref: http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Reference:Global_Objects:Array:forEach
     */
    forEach: function(fun/*, thisp*/) {
		var len = this.length;
		if (typeof fun != "function") {
			throw new TypeError();
		}

		var thisp = arguments[1];
		for (var i = 0; i < len; ++i) {
		  if (i in this) fun.call(thisp, this[i], i, this);
		}
	}
});
/*
 * DOM utilites
 * creator: lifesinger@gmail.com
 */

Unicorn.util.Dom = { };
Unicorn.lang.augmentObject(Unicorn.util.Dom, YAHOO.util.Dom);

Unicorn.lang.apply(Unicorn.util.Dom, function() {
	var L = Unicorn.lang,
		D = Unicorn.util.Dom;
	
	/* private memembers */
		var propertyCache = { }, // for faster hyphen converts
			patterns = { // regex cache
		        HYPHEN: /(-[a-z])/i // to normalize get/setStyle
		    };
		
		var toCamel = function(property) {
	        if ( !patterns.HYPHEN.test(property) ) {
	            return property; // no hyphens
	        }
	        
	        if (propertyCache[property]) { // already converted
	            return propertyCache[property];
	        }
	       
	        var converted = property;
	 		
	        while( patterns.HYPHEN.exec(converted) ) {
	            converted = converted.replace(RegExp.$1,
	                    RegExp.$1.substr(1).toUpperCase());
	        }
	        
	        propertyCache[property] = converted;
	        return converted;
	    };
		
		// A method for quickly swapping in/out CSS properties to get correct calculations
		var cssSwap = function(elem, props, callback) {
			var old = { };
			// Remember the old values, and insert the new ones
			for (var name in props) {
				old[name] = elem.style[name];
				elem.style[name] = props[name];
			}

			callback.call(elem);

			// Revert the old values
			for ( var name in props )
				elem.style[name] = old[name];
		};
	 /* end of private memembers */
	
	return {
		/**
		 * 切换元素的class
		 * 只有两个参数el和classA时，如果el有classA，则去掉classA，如果没有，则加上
		 * 有三个参数，当el有classA或classB时，将classA和classB互换；如果classA和classB在el中都没有，不进行任何操作
		 * Ref: jQuery 1.2.6
		 *
		 * @param {String | HTMLElement} el
		 * @param {String} classA
		 * @param {String} [optional] classB
		 */
		toggleClass: function(el, classA, classB) {
			el = D.get(el);
			if(!el || !classA) return;
			
			if(D.hasClass(el, classA)) {
				if(classB) {
					D.replaceClass(el, classA, classB);
				} else {
					D.removeClass(el, classA);
				}
			} else {
				if (classB && D.hasClass(el, classB)) {
					D.replaceClass(el, classB, classA);
				} else {
					D.addClass(el, classA);
				}
			}
		},
		
		/**
		 * Get the computed style
		 * See: YUI 3.x getComputedStyle
		 * @param el 单个元素，不支持数组
		 * @param property
		 * 注意：只转换能够转换的单值，如marginTop，其它值不做转换，如 margin, 在ie和firefox下，margin的返回值不同
		 * YUI 3.x PR1里，node.getComputedStyle('margin') 返回空值，感觉不妥
		 */
		getComputedStyle: function(el, prop) {
			el = D.get(el);
			var val = D.getStyle(el, prop);
			
			//var re = /^\d+(px)?$/i;
			//if(re.test(val)) {
			if(!val || val.indexOf('px') > -1) {
				return val;
			}
			
			// convert to pixel
			// font-size: 120%  百分比的转换目前不支持，在ie下不对
			// YUI 3.x PR1 也存在这个bug
			// 这里对于百分比，不做转换，直接返回
			var reUnit = /^(\d[.\d]*)+(em|ex|px|gd|rem|vw|vh|vm|ch|mm|cm|in|pt|pc|deg|rad|ms|s|hz|khz){1}?$/i;
			if(reUnit.test(val)) {
				val = getPixel(el, prop, val);
			}
			
			// width/height 'auto'
			// Ref: jquery 1.2.6  css function
			if(val == 'auto' && (prop == 'width' || prop == 'height')) {
				if (D.getStyle(el, 'display') != 'none') { // display为none时，clientWidth等属性全为0
					val = getWH(el, prop);
				} else {
					cssSwap(el, { visibility: 'hidden', display:'block' }, function() {
						val = getWH(el, prop);
					});
				}
			}
			
			return val;
			
			// http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291
			function getPixel(element, property, value) {
				if (document.defaultView && document.defaultView.getComputedStyle) { // W3C DOM method	
					var computed = element.ownerDocument.defaultView.getComputedStyle(element, '');
		            if (computed) { // test computed before touching for safari
		                value = parseFloat(computed[toCamel(property)]);
		                value = Math.round(value) + 'px';
		            }		            
		    	}
				else if(document.documentElement.currentStyle && Unicorn.env.ua.ie) { // IE method
					// Remember the original values
					var right = element.style.right;

					// Put in the new values to get a computed value out
					element.style.right = value;
					value = element.style.pixelRight + 'px';

					// Revert the changed values
					element.style.right = right;
				}
				
				return value;
			}
			
			// 获取高宽的实际值
			function getWH(element, property) {
				var isWidth = (property == 'width');
				var directs = isWidth ? ['Left', 'Right'] : ['Top', 'Bottom'];
				var padding = 0, border = 0;
				directs.forEach(function(direct) {
					// 没有考虑ie下获取border时，有可能得到medium, thin, 这时统统归一为0
					padding += parseFloat(D.getComputedStyle(element, 'padding' + direct)) || 0;
					border += parseFloat(D.getComputedStyle(element, 'border' + direct + 'Width')) || 0;
				});

				var value = isWidth ? element.offsetWidth : element.offsetHeight;
				value -= Math.round(padding + border);
				return value + 'px';
				
				// 不用clientWidth, 采用offsetWidth的理由: http://lifesinger.org/blog/?p=47
			}
		},
		
		/**
		 * 根据ClassName获取节点的第一个Child
		 * 未获取成功时返回null
		 */
		getFirstChildByClassName: function(node, className) {
			return D.getFirstChildBy(node, function(child) { return D.hasClass(child, className); });
		}
	};
}());

/*
 * Event utilites
 * creator: lifesinger@gmail.com
 */

Unicorn.util.Event = { };
Unicorn.lang.augmentObject(Unicorn.util.Event, YAHOO.util.Event);

Unicorn.lang.apply(Unicorn.util.Event, function() {
	var L  = Unicorn.lang,
		U  = Unicorn.util,
		D  = U.Dom;

	return {
		/**
		 * 说明：YAHOO.util.Event.on([], 'click', func) 会产生错误
		 * 这里包装一下，使得 E.on([], ...) 和 E.on(null, ...) 行为一致，不产生错误
		 * 等YUI改进了，这里直接删掉即可
		 */
		addListener: function(el, sType, fn, obj, override) {
			if(L.isArray(el) && el.length == 0) return false;
			
			return YAHOO.util.Event.addListener(el, sType, fn, obj, override);
		},
		
		/**
		 * 同 addListener
		 */
		on: function(el, sType, fn, obj, override) {
			return this.addListener(el, sType, fn, obj, override);
		}
	};
}());

/*
 * common utilites
 * ref:
 *  - Ext/Ext.js 2.1
 *
 * creator: lifesinger@gmail.com
 */

Unicorn.lang.apply(Unicorn.util, function() {
	var L = Unicorn.lang;
	
	return {
		/**
		 * Takes an object and converts it to an encoded URI-like query.
		 * <code>
		 | {foo: 1, bar: 2}	// -> 'foo=1&bar=2'
		 * </code>
		 * Optionally, property values can be arrays, instead of keys 
		 * and the resulting string that's returned will contain a name/value pair for each array value.
		 * @param {Object} o
		 * @return {String}
		 */
		encodeUriQuery: function(o) {
		    if(!o) return '';

		    var buf = [];
		    for(var key in o) {
		        var ov = o[key], k = encodeURIComponent(key);
		        var type = typeof ov;
		        
		        if(type == 'undefined') {
		            buf.push(k, '=&');
		        }
		        else if(type != 'function' && type != 'object') {
		            buf.push(k, '=', encodeURIComponent(ov), '&');
		        }
		        else if(L.isArray(ov)) {
		            if (ov.length) {
		                for(var i = 0, len = ov.length; i < len; i++) {
		                	var t = typeof ov[i];
		                	if(t != 'function' && t != 'object') {
		                    	buf.push(k, '=', encodeURIComponent(ov[i] === undefined ? '' : ov[i]), '&');
		                    }
		                }
		            }
		            else {
		                buf.push(k, '=&');
		            }
		        }
		    }
		    
		    buf.pop();
		    return buf.join('');
		},
		
		/**
		 * Parses a URI-like query string and returns an object composed of parameter/value pairs.
		 * This method is realy targeted at parsing query strings (hence the default value of "&" for the separator argument).
		 * For this reason, it does not consider anything that is either before a question 
		 * mark (which signals the beginning of a query string) or beyond the hash symbol ("#"), 
		 * and runs decodeURIComponent() on each parameter/value pair.
		 * Note that parameters which do not have a specified value will be set to undefined.
		 * <code>
		 | 'section=blog&id=45'		// -> {section: 'blog', id: '45'}
		 |
		 | 'section=blog;id=45', false, ';'		// -> {section: 'blog', id: '45'}
		 |
		 | 'http://www.example.com?section=blog&id=45#comments'		// -> {section: 'blog', id: '45'}
		 |
		 | 'section=blog&tag=javascript&tag=prototype&tag=doc'
		 | // -> {section: 'blog', tag: ['javascript', 'prototype', 'doc']}
		 |
		 | 'tag=ruby%20on%20rails'		// -> {tag: 'ruby on rails'}
		 |
		 | 'id=45&raw'		// -> {id: '45', raw: undefined}
		 * </code>
		 * @param {String} string
		 * @param {Boolean} overwrite (optional) Items of the same name will overwrite previous values instead of creating an an array (Defaults to false).
		 * @return {Object} A literal with members
		 */
		decodeUriQuery: function(string, overwrite, separator) {
			if(!string || !string.length) return { };
		    
			var match = string.trim().match(/([^?#]*)(#.*)?$/);
			if (!match) return { };
			
		    var obj = { };
		    var pairs = match[1].split(separator || '&');
		    
		    var pair, name, value;
		    for(var i = 0, len = pairs.length; i < len; ++i) {
		        pair = pairs[i].split('=');
		        name = decodeURIComponent(pair[0]);
		        value = decodeURIComponent(pair[1]);
		        if(value === '' || value === 'undefined') value = undefined; // &k 和 &k= 都还原成 undefined
		        
		        if(overwrite !== true) {
		            if(typeof obj[name] == 'undefined') {
		                obj[name] = value;
		            } else if(typeof obj[name] == 'string') {
		                obj[name] = [obj[name]];
		                obj[name].push(value);
		            } else {
		                obj[name].push(value);
		            }
		        } else {
		            obj[name] = value;
		        }
		    }
		    return obj;
		}
	};
}());

/*
 * Effect utilites
 * creator: lifesinger@gmail.com
 */
Unicorn.util.Effect = { };

Unicorn.lang.apply(Unicorn.util.Effect, function() {
	var L  = Unicorn.lang,
		U  = Unicorn.util,
		D  = U.Dom,
		E  = U.Event;
		
	var slideAnim = [ ];
	
	return {
		/**
		 * 通过高度/宽度的变化来显示/隐藏元素
		 * @param el 元素
		 * @param directions 可取 'x', 'y', 'xy', '-x+y'等，-代表缩小，+代表增大
		 * @config duration 动画持续时间。单位为秒，缺省值为0.25秒
		 * @config easing 默认为 Easing.easeNone
		 * @config callback 在动画完成时执行的回调函数
		 * @config thisObj callback里的this object
		 * @param force 强制执行，在手风琴等效果里，需要采用jQuery风格的动画
		 */
		slide: function(el, directions, config, force) {
			el = D.get(el);
			if(!el) return;
			
			// 采用Ext风格，当连续点击时，只有当当前动画完成之后的点击才生效
			// 值得注意的是jQuery采取的风格是依次排序，多次连续点击后，所有动画依次执行
			var key = el.id || D. generateId(el, 'unicorn-gen');
			if( (force !== true) && (slideAnim[key] && slideAnim[key].isAnimated())) {
				//YAHOO.log('isAnimated = ' + slideAnim[el].isAnimated(), 'info', 'Effect');
				return;
			}
			
			directions = directions || '-y'; // 默认为-y
			config = L.applyIf(config || { }, {
						duration: 0.25,
						easing: Unicorn.util.Easing.easeNone,
						thisObj: el
					});
					
			// parse directions
			//  0 代表此方向无动画
			//  1 代表增大
			// -1 代表缩小
			var directs = { x: 0, y: 0 };
			['x', 'y'].forEach(function(axis) {
				if(directions.indexOf(axis) > -1) {
					if(directions.indexOf('-' + axis) > -1) {
						directs[axis] = -1;
					} else { // x 或 +x
						directs[axis] = 1;
					}
				}
			});

			// remember original style values
			var originalStyle = {
				width: el.style.width,
				height: el.style.height,
				overflow: el.style.overflow
				};

			// get computed size  unit: px
			var size = { width: D.getComputedStyle(el, 'width'), height: D.getComputedStyle(el, 'height') };
			
			// set attributes
			var attributes = { };
			if(directs.x != 0) {
				attributes.width = { to: (directs.x > 0) ? parseFloat(size.width) : 0 };
			}
			if(directs.y != 0) {
				attributes.height = { to: (directs.y > 0) ? parseFloat(size.height) : 0 };
			}
			
			slideAnim[key] = new Unicorn.util.Anim(el, attributes, config.duration, config.easing);
			slideAnim[key].onComplete.subscribe(function() { 
					// 防止padding等因素导致隐藏不完全
					if(directs.x < 0 || directs.y < 0) D.setStyle(el, 'display', 'none');
					
					// Revert the original style values
					el.style.width = originalStyle.width;
					el.style.height = originalStyle.height;
					el.style.overflow = originalStyle.overflow;
					
					// free
					slideAnim[key] = null;			
				});
			if(config.callback) slideAnim[key].onComplete.subscribe(config.callback, config.thisObj, true);
			
			// Make sure that nothing sneaks out
			if(directs.y < 0 && Unicorn.env.ua.ie) {
				// ie7中，某些情况下(具体请参考 http://lifesinger.org/blog/?p=47)，
				// 设置D.setStyle(el, 'overflow', 'hidden');将导致offsetHeight的值不对
				// 这将导致slideUp时，元素垂直方向突然变小
				// jQuery 1.2.6中有这个bug
				// 为了避免这种情况，在设置overflow hidden时，先设置好height
				el.style.height = size.height; // 在动画最后会还原，属于无侵入
			}
			D.setStyle(el, 'overflow', 'hidden');
			
			// 调整到动画开始状态
			if(directs.x > 0) D.setStyle(el, 'width', '0');
			if(directs.y > 0) D.setStyle(el, 'height', '0');
			if(D.getStyle(el, 'display') == 'none') D.setStyle(el, 'display', '');
			
			// go
			slideAnim[key].animate();	
		},
		slideToggle: function(el, direction, config, force) {
			var minus = (D.getStyle(el, 'display') != 'none') ? '-' : '';			
			var directions = minus + (direction == 'x' ? 'x' : 'y');
			
			this.slide(el, directions, config, force);
		},
		slideDown: function(el, config, force) {
			this.slide(el, 'y', config, force);
		},
		slideUp: function(el, config, force) {
			this.slide(el, '-y', config, force);
		},
		
		/**
		 * 通过透明度的变化来淡入/淡出元素
		 * @param el 元素
		 * @param opacity 0到1之间的数字
		 * @config duration 动画持续时间。单位为秒，缺省值为0.25秒
		 * @config easing 默认为 YAHOO.util.Easing.easeNone
		 * @config callback 在动画完成时执行的回调函数
		 * @config thisObj callback里的this object
		 */
		fadeTo: function(el, opacity, config) {
			el = D.get(el);
			if(!el) return;
			
			opacity = L.isNumber(opacity) ? opacity : 1; // 默认为1
			config = L.applyIf(config || { }, {
						duration: 0.25,
						thisObj: el
					});
			
			var attributes = { opacity: { to: opacity } };
			var anim = new Unicorn.util.Anim(el, attributes, config.duration, config.easing);
			if(config.callback) anim.onComplete.subscribe(config.callback, config.thisObj, true);
			
			anim.animate();	
		},
		fadeIn: function(el, config) {
			this.fadeTo(el, 1, config);
		},
		fadeOut: function(el, config) {
			this.fadeTo(el, 0, config);
		}
	};
}());

/**
 * 实现widget时的一些辅助方法
 *
 * 注意：目前还未想清楚如何组织这些代码，属于私有类，外部请勿调用
 * creator: lifesinger@gmail.com
 */

Unicorn.widget.WidgetHelper = { };

Unicorn.lang.apply(Unicorn.widget.WidgetHelper, function() {
	var L  = Unicorn.lang,
		U  = Unicorn.util,
		D  = U.Dom,
		E  = U.Event;
	
	return {
		/**
		 * Used to add events on object
		 * eg. addEvents.apply(this, ['onCollapse', 'beforeCollapse', 'onExpand', 'beforeExpand']);
		 */
		addEvents: function() {
			if(!this.events) this.events = { };
			
			var obj = this;
			var eventNames = arguments;
			for(var i = 0, len = eventNames.length; i < len; ++i) {
				(function() {
					var eventName = eventNames[i];
					if(typeof eventName != 'string') return;
					
					obj.events[eventName] = new Unicorn.util.CustomEvent(eventName, null, false, Unicorn.util.CustomEvent.FLAT);
					
					if(obj.config && obj.config.events && obj.config.events[eventName]) {
						obj.events[eventName].subscribe(obj.config.events[eventName]);
					}

					obj[eventName] = obj.events[eventName];
				})();
			}
		},
		
		/**
		 * 解析参数
		 */
		parseArguments: function(args, firstArgName) {
			var validArgs = [];
			for(var i = 0, len = args.length; i < len; ++i) {
				if(typeof args[i] != 'undefined')
					validArgs.push(args[i]);
			}
			args = validArgs;
			
			var cfg1, cfg2;
			firstArgName = firstArgName || 'container';
			len = args.length;
			
			if(len == 1) {
				if(typeof args[0] == 'string' || args[0].nodeType == 1) { // new WidgetName(firstArgValue)
					cfg1 = {};
					cfg1[firstArgName] = args[0];
				} else if(typeof args[0] == 'object') { // new WidgetName( {firstArgName: 'value', ... } )
					cfg1 = args[0];
				}
			} else if(len == 2) {
				// new WidgetName(firstArgValue, {secondArgName: 'value', ...} )
				if((typeof args[0] == 'string' || args[0].nodeType == 1) && typeof args[1] == 'object') {
					cfg1 = args[1];
					cfg1[firstArgName] = args[0];
				// new WidgetName(cfg1, cfg2)
				} else if(typeof args[0] == 'object' && typeof args[1] == 'object') {
					cfg1 = args[0];
					cfg2 = args[1];
				}
			} else if(len == 3) {
				// new WidgetName(firstArgValue, cfg1, cfg2)
				if((typeof args[0] == 'string' || args[0].nodeType == 1) && typeof args[2] == 'object') {
					cfg1 = args[1] || { }; // args[1]可以为null
					cfg1[firstArgName] = args[0];
					cfg2 = args[2];
				}
			}
			
			return [cfg1, cfg2];
		}
	};
}());/**
 * @class Unicorn.widget.InputMask
 * creator: lifesinger@gmail.com
 */

/**
 * members:
 *   this.config
 *   this.inputEl
 *   this.value // 当前有效值
 *   this.events: 'onRightInput', 'onErrorInput', 'onFinish', 'onPass', 'onError'
 */

Unicorn.widget.InputMask = function() {
	var L  = Unicorn.lang,
		U  = Unicorn.util,
		D  = U.Dom,
		E  = U.Event,
		helper = Unicorn.widget.WidgetHelper;

	// 默认配置
	var defConfig = {
			inputEl: null, // input 元素
			formatMask: /^.*$/, // 输入的整个值需要满足的正则
			keyMask: /./, // 输入的键值需要满足的正则
			minLength: 0, // 仅在onblur时，做校验用
			maxLength: -1, // 如果input中设置了maxlength, init时会将maxLength的默认值设为maxlength
			chineseCharLength: 2, // 一个汉字默认算2个字符
			events: { }
		};

	var InputMask = function() {
		var cfgs = helper.parseArguments(arguments, 'inputEl');
		
		// 获取默认配置
		this.config = L.applyIf(cfgs[0] || {}, defConfig);
		
		init.call(this);
	};

	function init(config) {
		var cfg = this.config;
		this.inputEl = D.get(cfg.inputEl);
		if(!this.inputEl) return;
		
		if(!(cfg.keyMask instanceof RegExp)) return; // 不是正则表达式，直接返回
		if(!(cfg.formatMask instanceof RegExp)) return;
		
		// 如果配置里没有指定maxLength, 则从input的maxlength属性中获取
		if(cfg.maxLength == '-1' && this.inputEl.getAttribute('maxlength')) {
			cfg.maxLength = this.inputEl.getAttribute('maxlength');
		}
		this.value = this.inputEl.value || '';
		
		// 添加事件
		helper.addEvents.apply(this, ['onRightInput', 'onErrorInput', 'onFinish', 'onPass', 'onError']);
		
		// 考虑到输入法未打开时，通过keypress实现的阻止非法字符输入效果最好，
		// 因此依旧保留对此事件的监听（其它方法阻止非法字符时，会先显示出来，再自动删掉）
		E.on(this.inputEl, 'keypress', function(e) {
			//Unicorn.log('keypress fired.', 'info', 'InputMask');
			var keyCode = getKeyCode(e);
			
			// 在Opera下，非打印字符也会触发keypress
			// 通过isPrintable判断，使得Opera和其它浏览器行为一致
			// isPrintable还可以将输入法开启时的输入拦截掉，统一到后面处理
			if(!keyCode || !isPrintable(keyCode)) return;

			// firefox中，ctrl + c/v/x/z/y等组合键会触发keypress，应过滤掉
			// 否则无法使用快捷键来进行复制粘贴操作
			if(e.ctrlKey || e.altKey) return;
			
			var key = String.fromCharCode(keyCode);
			//Unicorn.log('keypress: key = ' + key, 'info', 'InputMask');
				
			if(!cfg.keyMask.test(key)) {
				this.events['onErrorInput'].fire(this);
				E.stopEvent(e);
			}
		}, null, this);
		
		// 监听input/propertychange
		var eventName = Unicorn.env.ua.ie ? 'propertychange' : 'input';
		E.on(this.inputEl, eventName, function(e) {
			//Unicorn.log(eventName + ' fired.', 'info', 'InputMask');
			// 对于propertychange事件，只检测value值的改变
			if(eventName == 'propertychange' && e.propertyName != 'value') return;
			
			updateInputValue.apply(this);
		}, null, this);
		
		// 触发输入完成事件
		E.on(this.inputEl, 'blur', function(e) {
			//Unicorn.log('blur fired. Current input value = ' + this.inputEl.value, 'info', 'InputMask');
			
			this.events['onFinish'].fire(this);
			this.value = this.inputEl.value; // 更新value，可能在onFinish事件中有改动
			
			// 最后验证是否满足format
			if(cfg.formatMask instanceof RegExp) {
				if(cfg.formatMask.test(this.value) 
				   && getValidLength(this.value, this.config.chineseCharLength) >= this.config.minLength) {
					this.events['onPass'].fire(this);
				} else {
					this.events['onError'].fire(this);
				}
			}
		}, null, this);
	};

	/* private members */
		// regex cache
		var patterns = {
			CHINESE_CHAR: /[\u4e00-\u9fa5]/ // GBK编码范围
		};
		
		/**
		 * Returns true if the key is a printable character.
		 */
		function isPrintable(keyCode) {
			return (keyCode >= 32 && keyCode < 127);
		}

		/**
		* Returns the key code associated with the event.
		*/
		function getKeyCode(e) {
		  return window.event ? window.event.keyCode : (e ? e.which : null);
		}
		
		/**
		 * 将值中的非法字符去掉
		 */
		function getValidValue(val, regex) {
			//Unicorn.log('getValidValue BEGIN: val = ' + val, 'info', 'InputMask');
			var tchars = val.split('');
			var chars = [];
			// 直接遍历获取满足要求的字符
			for(var i = 0, len = tchars.length; i < len; ++i) {
				if(regex.test(tchars[i])) {
					chars.push(tchars[i]);
				}
			}
			val = chars.join('');
			//Unicorn.log('getValidValue END: val = ' + val, 'info', 'InputMask');
			return val;
		}
		
		/**
		 * 检查输入值的长度是否有效
		 */
		function checkLength(val, maxLength, chineseCharLength) {
			if(maxLength == -1) return true; // -1说明长度无限制
			if(!val || val.length == 0) return true; // 输入值为空
			
			var len = getValidLength(val, chineseCharLength);
			return  len <= maxLength;
		}
		
		/**
		 * 获取有效长度
		 */
		function getValidLength(val, chineseCharLength) {
			var arr = val.split('');
			var len = 0;
			for(var i = 0, l = arr.length; i < l; ++i) {
				if(patterns.CHINESE_CHAR.test(arr[i])) {
					len += chineseCharLength;
				} else {
					len += 1;
				}
			}		
			//Unicorn.log('getValidLength: length = ' + len, 'info', 'InputMask');
			return len;
		}
		
		/**
		 * 更新输入值
		 */
		function updateInputValue() {
			var inputValue = this.inputEl.value;
			if(this.value == inputValue || inputValue.legnth == 0) return; // 没有变化，说明在keypress阶段已经成功阻止了非法输入，这里无需再处理
			
			// 值有变化时，直接将值中的非法字符去掉（输入时的位置变化，粘贴等情况都没问题）
			inputValue = getValidValue(inputValue, this.config.keyMask);
			
			// 检查长度
			var lengthIsOK = checkLength(inputValue, this.config.maxLength, this.config.chineseCharLength);
			
			if(inputValue == this.inputEl.value // 新输入值不是非法字符
			   && lengthIsOK) { // 小于等于最大长度
				this.events['onRightInput'].fire(this);
			} else {
				// 定时器方法中，firefox下，输入法打开时，下面这句会导致inputEl的value置空
				// 遗留问题：Safari下，输入法打开时，下面这句会导致input的焦点丢失
				this.inputEl.value = lengthIsOK ? inputValue : this.value; // 超出长度时，直接还原
				
				this.events['onErrorInput'].fire(this);
			}
			// 更新this.value
			this.value = this.inputEl.value;
		}	
	/* end of private members */
	
	return InputMask;
}();

// 增加decorate静态方法
Unicorn.widget.InputMask.decorate = function(inputEl, config) {
	return new Unicorn.widget.InputMask(inputEl, config);
};

/**
 * @class Unicorn.widget.SimpleInput
 * creator: lifesinger@gmail.com
 */

Unicorn.widget.SimpleInput = function() {
	var L  = Unicorn.lang,
		U  = Unicorn.util,
		D  = U.Dom,
		E  = U.Event,
		helper = Unicorn.widget.WidgetHelper;

	// SimpleInput的默认配置
	// 和InputMask相同的配置项请参考InputMask.source.js
	var defConfig = {
			tipsCls: 'tips',
			warningCls: 'warning',
			passedCls: 'passed',
			errorCls: 'error',
			currentCls: 'current'
		};

	var SimpleInput = function() {
		var cfgs = helper.parseArguments(arguments, 'inputEl');
		
		// 获取默认配置
		var config = L.applyIf(cfgs[0] || {}, defConfig);
		
		// chain ctor
		this.constructor.superclass.constructor.call(this, config);
		
		init.call(this);
	};
	
	function init() {
		var cfg = this.config;
		var tips = D.getElementsByClassName('tips', '*', this.inputEl.parentNode)[0];
		
		E.on(this.inputEl, 'focus', function() {
			// 还原样式
			D.removeClass(this.parentNode, cfg.errorCls);
			D.removeClass(tips, cfg.warningCls);
			D.removeClass(tips, cfg.passedCls);
			
			D.addClass(this.parentNode, cfg.currentCls);
		});
		
		this.onRightInput.subscribe(function() {
			//Unicorn.log('onRightInput handler fired.', 'pass', 'testCase');
			D.removeClass(tips, cfg.warningCls);
		});
		
		this.onErrorInput.subscribe(function() {
			//Unicorn.log('onErrorInput handler fired.', 'pass', 'testCase');
			D.addClass(tips, cfg.warningCls);
		});
		
		this.onFinish.subscribe(function() {
			//Unicorn.log('onBlur handler fired. inputEl.value = ' + inputEl.value, 'pass', 'testCase');
			D.removeClass(this.inputEl.parentNode, cfg.currentCls);
		}, null, this);
		
		this.onError.subscribe(function() {
			//Unicorn.log('onError handler fired.', 'pass', 'testCase');
			D.addClass(this.inputEl.parentNode, cfg.errorCls);
			D.addClass(tips, cfg.warningCls);
		}, null, this);
		
		this.onPass.subscribe(function() {
			//Unicorn.log('onPass handler fired.', 'pass', 'testCase');
			D.removeClass(this.inputEl.parentNode, cfg.errorCls);
			D.removeClass(tips, cfg.warningCls);
			D.addClass(tips, cfg.passedCls);
		}, null, this);
	};
	
	Unicorn.extend(SimpleInput, Unicorn.widget.InputMask);
	return SimpleInput;
}();

// 增加decorate静态方法
Unicorn.widget.SimpleInput.decorate = function(inputEl, config) {
	return new Unicorn.widget.SimpleInput(inputEl, config);
};/**
 * @class Unicorn.widget.Panel
 * ref:
 *  - Ext/Ext.js 2.1
 *
 * creator: lifesinger@gmail.com
 */
 
/**
 * members:
 * 	 this.container
 * 	 this.config
 * 	 this.head, this.body, this.foot
 *   this.collapsed, this.isAnimating
 * 	 this.events: onCollapse, beforeCollapse, onExpand, beforeExpand
 * 	 prototype: getDom(), collapse(), expand(), toggleCollapse()
 */

Unicorn.widget.Panel = function() {
	var L  = Unicorn.lang,
		U  = Unicorn.util,
		D  = U.Dom,
		E  = U.Event,
		Ef = U.Effect,
		helper = Unicorn.widget.WidgetHelper;

	// 默认配置
	var defConfig = {
			container: null, 
			
			// 将优先根据cls来获取panel的head, body, foot
			headCls: 'panel-hd',
			bodyCls: 'panel-bd',
			footCls: 'panel-ft',
				
			collapsible: false, // 是否可以收缩，只有为true时，下面相关配置才生效
			collapsed: false, // 初始是否处于收缩状态
			collapsedCls: 'collapsed', // 处于收缩状态时，给container附加的cls
			titleCollapse: false, // 整个title在点击时都触发collapse切换
			disableTriggerClick: true, // 禁止触发器的点击事件
			
			// 工具条上的cls
			toolBaseCls: 'tool',
			toolOverCls: 'over',
			toolToggleCls: 'toggle',
			
			animCollapse: L.isObject(U.Anim), // 收缩时，是否有动画效果。默认值取决于Anim有没有加载
			animDuration: 0.25,
			animEasing: U.Easing.easeNone,
			
			/* 保持简单，可拖动性等暂时不考虑实现
			draggable: false,
			draggableCls: 'draggable-panel',
			//shadow: null, // TODO
			*/
			
			events: { }
		};
	
	var Panel = function() {
		var cfgs = helper.parseArguments(arguments);
		
		// 获取默认配置
		this.config = L.applyIf(cfgs[0] || {}, defConfig);
		
		// 如果Anim没加载，animCollapse应该强制为false
		if(!L.isObject(U.Anim)) {
			this.config.animCollapse = false;
		}

		init.call(this);
	};

	function init() {
		var cfg = this.config;
		this.container = D.get(cfg.container);
		if(!this.container) return;
		
		// 初始化panel的各个部分
		initParts.call(this);
		
		// init collapse
		// 限制：必须有头部时才能collapse，不考虑其它情况
		if(cfg.collapsible && this.head) {
			initCollapse.call(this);
		}

		// init DD
		/*if(DD && cfg.draggable && this.head) { // 只支持头部的拖动
			initDD.call(this);
		}*/
	};
	
	// private members
		/**
		 * 得到panel的head, body, foot
		 */
		function initParts() {
			var cfg = this.config;
			
			// 优先根据className来获取
			this.head = D.getFirstChildByClassName(this.container, cfg.headCls);
			this.body = D.getFirstChildByClassName(this.container, cfg.bodyCls);
			this.foot = D.getFirstChildByClassName(this.container, cfg.footCls);
			
			// 上面未获取到时，再根据默认规则来获取
			// 原则：给与适当的灵活性
			var children = D.getChildren(this.container);
			var len = children.length;
			// 含有两个子元素时，默认为head和body
			if(!this.head && !this.body && len == 2) {
				this.head = children[0];
				this.body = children[1];
			}
			// 含有三个子元素时，默认依次赋值
			if(!this.head && !this.body && !this.foot && len == 3) {
				this.head = children[0];
				this.body = children[1];
				this.foot = children[2];
			}
			
			if(!this.body) { // body是必须有的
				throw new Error('The Config of Panel has something wrong.');
			}
		}
		
		/**
		 * init collapse
		 */
		function initCollapse() {
			var cfg = this.config;
			
			// 先从头部获取toggle，未获取到时则自动创建
			var toggleCls = cfg.toolBaseCls + '-' + cfg.toolToggleCls;
			var toolToggle = D.getFirstChildByClassName(this.head, toggleCls);
			if(!toolToggle) { // 没有时，自动创建
				toolToggle = document.createElement('div');
				D.addClass(toolToggle, cfg.toolBaseCls);
				D.addClass(toolToggle, toggleCls);
				this.head.appendChild(toolToggle);
			}
			
			// init trigger
			var toggleTrigger = cfg.titleCollapse ? this.head : toolToggle;
			// 交给css去处理。在这里加上，会导致某些情况下不灵活（比如我的淘宝菜单）
			//D.setStyle(toggleTrigger, 'cursor', 'pointer');

			var normalCls = cfg.toolBaseCls + '-' + cfg.toolToggleCls;
			var overCls = cfg.toolBaseCls + '-' + cfg.toolToggleCls + '-' + cfg.toolOverCls;			
			var toggleToolCls = function() {
				D.toggleClass(toolToggle, normalCls, overCls);
			};
			E.on(toolToggle, 'mouseover', toggleToolCls);
			E.on(toolToggle, 'mouseout', toggleToolCls);
			
			E.on(toggleTrigger, 'click', function(e) {
				if(cfg.disableTriggerClick !== true && E.getTarget(e).nodeName == 'A') return;
				
				E.preventDefault(e);
				this.toggleCollapse();
			}, null, this);
			
			// 添加自定义事件
			helper.addEvents.apply(this, ['onCollapse', 'beforeCollapse', 'onExpand', 'beforeExpand']);
			
			// 初始为collapsed状态时
			this.collapsed = false;
			if(cfg.collapsed) {
				this.collapse(false);
			}
		}
		
		/**
		 * init DD
		 */
		/*function initDD() {
			var cfg = this.config;
			var dd = new DD(cfg.container);
			dd.setHandleElId(this.head);
			
			D.addClass(this.container, cfg.draggableCls);
			D.setStyle(this.head, 'cursor', 'move');
		}*/
	// end of private members
	
	Panel.prototype = {
		/**
		 * 获取与panel相关联的dom元素
		 */
		getDom: function() {
			return this.container;
		},
		
		/**
		 * Collapses the panel body so that it becomes hidden. 
		 * Fires the beforeCollapse event which will cancel the collapse action if it returns false.
		 *
		 * @param {Boolean} anmiate True to animate the transition, else false (defaults to the value of the animCollapse config)
		 */
		collapse: function(animate, force) {
			// force表示强制执行
			if((force !== true) && (this.isAnimating || this.collapsed)) return;
			if(this.events['beforeCollapse'].fire(this) === false) return;

			if(typeof animate != 'boolean') animate = this.config.animCollapse;
			if(animate) {				
				var callback = 	function() {
					this.collapsed = true;
					D.addClass(this.getDom(), this.config.collapsedCls);
					this.events['onCollapse'].fire(this);
					this.isAnimating = false;
				};
				Ef.slideUp(this.body, { duration: this.config.animDuration, easing: this.config.animEasing, callback: callback, thisObj: this }, force);
			} else {
				D.setStyle(this.body, 'display', 'none');
				this.collapsed = true;
				D.addClass(this.getDom(), this.config.collapsedCls);
			}
			
			this.isAnimating = animate;
			Unicorn.log(new Date().toLocaleTimeString() + ' collapse fired', 'info', 'Panel');
		},
		expand: function(animate, force) {
			if((force !== true) && (this.isAnimating || !this.collapsed)) return;
			if(this.events['beforeExpand'].fire(this) === false) return;
			
			this.collapsed = false;
			D.removeClass(this.getDom(), this.config.collapsedCls);
			
			if(typeof animate != 'boolean') animate = this.config.animCollapse;
			if(animate) {
				var callback = 	function() {
					this.events['onExpand'].fire(this);
					this.isAnimating = false;
				};
				Ef.slideDown(this.body, { duration: this.config.animDuration, easing: this.config.animEasing, callback: callback, thisObj: this }, force);
 			} else {
 				D.setStyle(this.body, 'display', '');
 			}
			
			this.isAnimating = animate;
			Unicorn.log(new Date().toLocaleTimeString() + ' expand fired', 'info', 'Panel');
		},
		toggleCollapse: function(animate) {
			this[this.collapsed ? 'expand' : 'collapse'](animate);
		}
	};
	
	return Panel;
}();

// 增加decorate静态方法
Unicorn.widget.Panel.decorate = function(container, panelCfg) {
	return new Unicorn.widget.Panel(container, panelCfg);
};

/**
 * @class Unicorn.widget.PanelList
 * creator: lifesinger@gmail.com
 */
 
/**
 * members:
 *   this.container
 * 	 this.config, this.panelConfig
 * 	 this.panels
 */

Unicorn.widget.PanelList = function() {
	var L  = Unicorn.lang,
		U  = Unicorn.util,
		D  = U.Dom,
		E  = U.Event,
		Ef = U.Effect,
		helper = Unicorn.widget.WidgetHelper;

	// 默认配置
	var defConfig = {
			container: null,
			
			/**
			 * 1. recursive为false时，如果没指定panelCls，则获取所有子元素为panels；如果指定
			 *    了panelCls，则只获取class中有panelCls的子元素为panels
			 * 2. recursive为true时，必须指定panelCls，否则异常
			 */
			recursive: false, // 递归获取所有panels
			panelCls: ''
		};
	
	var PanelList = function() {
		var cfgs = helper.parseArguments(arguments);
		
		// 获取默认配置
		this.config = L.applyIf(cfgs[0] || {}, defConfig);
		this.panelConfig = cfgs[1] || {};
		
		// 初始化
		init.call(this);
	};

	function init() {
		var cfg = this.config;
		this.container = D.get(cfg.container);
		if(!this.container) return;
		
		// init panels
		var domPanels = [];
		
		if(cfg.recursive && cfg.panelCls) {
			domPanels = D.getElementsByClassName(cfg.panelCls, '*', this.container);
		} else if(!cfg.recursive) {
			domPanels = D.getChildrenBy(this.container, function(el) {
				if(cfg.panelCls) {
					return D.hasClass(el, cfg.panelCls);
				} else {
					// 只获取Element，对于其它node（如注释，style等），需要直接忽略
					return (el.nodeType == 1 && ['STYLE', 'SCRIPT'].indexOf(el.nodeName) == -1); 
				}
			});
		} else {
			throw new Error('The config of PanelList has something wrong.');
		}
		
		this.panels = [];
		
		for(var i = 0, len = domPanels.length; i < len; ++i) {
			this.panels.push(new Unicorn.widget.Panel(domPanels[i], this.panelConfig));
		}		
	}
	
	return PanelList;
}();

// 增加decorate静态方法
Unicorn.widget.PanelList.decorate = function(container, listCfg, panelCfg) {
	return new Unicorn.widget.PanelList(container, listCfg, panelCfg);
};/**
 * @class Unicorn.widget.FoldingList
 * creator: lifesinger@gmail.com
 */

/**
 * super: PanelList
 * members:
 *   prototype: expandAll(), collapseAll()
 */

Unicorn.widget.FoldingList = function() {
	var L  = Unicorn.lang,
		U  = Unicorn.util,
		D  = U.Dom,
		E  = U.Event,
		Ef = U.Effect,
		helper = Unicorn.widget.WidgetHelper;

	// FoldingList的默认配置
	// 和PanelList相同的配置项请参考PanelList.source.js
	var defConfig = {
			multiExpand: true  // true: 允许同时展开多个  false：同一时间只允许展开一个
		};
	
	var FoldingList = function() {
		var cfgs = helper.parseArguments(arguments);
		
		// 获取默认配置
		var config = L.applyIf(cfgs[0] || {}, defConfig);
		var panelConfig = cfgs[1] || {};
		
		// 设置特定配置
		panelConfig.collapsible = true; // 对于FoldingList来说，这个肯定是true，用户不能覆盖
		
		// chain ctor
		this.constructor.superclass.constructor.call(this, config, panelConfig);
		
		// 初始化
		init.call(this);
	};
	
	function init() {
		if(!this.container) return;
		
		// 处理单一展开
		if(!this.config.multiExpand) {
			processMultiExpand.call(this);
		}
	}
	
	// private members
		/*
		 * 处理单一展开
		 */
		function processMultiExpand() {
			var panels = this.panels;
			for(var i = 0, len = panels.length; i < len; ++i) {
				var panel = panels[i];
				panel.beforeExpand.subscribe(function() {
					// 展开此panel前，先将其它panel收缩起来
					for(var j = 0; j < len; ++j) {
						// 只收缩同级的其它面板
						var p = panels[j];
						if(p.getDom().parentNode == this.getDom().parentNode 
							&& p != this && !p.collapsed) {
							Unicorn.log('p.isAnimating = ' + p.isAnimating, 'info', 'FoldingList');
							p.collapse(null, p.isAnimating ? true : false);
						}
					}
				}, null, panel); 
			}
		}
	// end of private members
	
	Unicorn.extend(FoldingList, Unicorn.widget.PanelList, {
		expandAll: function() {
			if(!this.config.multiExpand) return;		
			for(var i = 0, len = this.panels.length; i < len; ++i) {
				this.panels.expand();
			}
		},
		collapseAll: function() {
			for(var i = 0, len = this.panels.length; i < len; ++i) {
				this.panels.collapse();
			}
		}
	});
	
	return FoldingList;
}();

// 增加decorate静态方法
Unicorn.widget.FoldingList.decorate = function(container, listCfg, panelCfg) {
	return new Unicorn.widget.FoldingList(container, listCfg, panelCfg);
};/**
 * @class Unicorn.widget.TabView
 * creator: lifesinger@gmail.com
 */

/**
 * members:
 *   this.config
 *   this.container, this.triggers, this.panels
 *   this.panels[i].trigger, this.panels[i].hash
 *   this.events: onSwitch, beforeSwitch
 *   prototype: setActiveItem()
 */

Unicorn.widget.TabView = function() {
	var L  = Unicorn.lang,
		U  = Unicorn.util,
		D  = U.Dom,
		E  = U.Event,
		helper = Unicorn.widget.WidgetHelper;

	// 默认配置
	var defConfig = {
			container: null,
			containerCls: 'tv-container',
			navBarCls: 'tv-nav',
			panelsWrapperCls: 'tv-wrapper',
			
			eventType: 'click', // or 'mouse'
			delay: 0.1,
			disableTriggerClick: true, // 禁止触发器的点击事件
			
			activeIndex: 0, // 为了避免闪烁，mackup的无js时默认激活的项，应该与此index一致
			parseHash: false, // 从location.hash中解析出activeIndex. 为true时，配置项activeIndex无效
			activeNavItemCls: 'current',
			
			events: { }
		};
	
	var TabView = function() {
		var cfgs = helper.parseArguments(arguments);
		
		// 获取默认配置
		this.config = L.applyIf(cfgs[0] || {}, defConfig);
		
		init.call(this);
	};
	
	function init() {
		var cfg = this.config;
		this.container = D.get(cfg.container);
		if(!this.container) return;
		
		helper.addEvents.apply(this, ['onSwitch', 'beforeSwitch']);
		
		this.parseMackup();
		initTriggersAndPanels.call(this);

		// 从location.hash中解析出activeIndex
		if(cfg.parseHash && location.hash) {
			var index = parseHash.call(this);
			
			// 当需要激活时才激活，尽量懒
			if(index != cfg.activeIndex) {
				this.setActiveItem(index);
			}
		}
	}
	
	// private members
		/**
		 * 初始化this.triggers和this.panels
		 */
		function initTriggersAndPanels() {
			var cfg = this.config;

			this.panels.forEach(function(panel, index) {
				var trigger = this.triggers[index];
				
				// init panel.trigger
				trigger.setAttribute('rel', index);
				
				// 添加触发器事件
				attachTriggerEvent.call(this, trigger, index);
				panel.trigger = trigger;
				
				// init hash
				if(cfg.parseHash) {
					panel.hash = '#tab' + index;
				}
				
				// 阻止掉panel.trigger里面a的点击事件
				if(cfg.disableTriggerClick === true) {
					var anchor = D.getFirstChildBy(trigger, function(child) { return child.nodeName == 'A'; });
					if(anchor) {
						E.on(anchor, 'click', function(e) { E.preventDefault(e); });
					}
				}
			}, this);
			
			// 将非当前页隐藏掉
			this.panels.forEach(function(panel) {
				if(!D.hasClass(panel.trigger, cfg.activeNavItemCls)) {
					D.setStyle(panel, 'display', 'none');
				}
			});
		}

		/**
		 * 给触发器添加事件
		 */
		function attachTriggerEvent(trigger, index) {
			var timer, cfg = this.config;
			
			if(this.config.eventType == 'mouse') {
				E.on(trigger, 'mouseover', function() {
					if(cfg.activeIndex == index) return; // 重复触发
					
					timer = L.later(cfg.delay*1000, this, this.setActiveItem, index);
				}, null, this);
				
				E.on(trigger, 'mouseout', function() {
					if(timer) timer.cancel();
				});
			
			} else { // 默认为'click'
				E.on(trigger, 'click', function(e) {
					if(cfg.activeIndex == index) return; // 重复触发
					
					this.setActiveItem(index);
				}, null, this);
			}
		}
		
		/**
		 * 从location.hash中获取activeIndex
		 */
		function parseHash() {
			for(var i = 0, len = this.panels.length; i < len; ++i) {
				var hash = this.panels[i].hash;
				if(hash == location.hash) {
					return i;
				}
			}
		}
		
		function getHtmlElementsChildren(parent) {
			return D.getChildrenBy(parent, function(el) {
					// 只获取Element，对于其它node（如注释，style等），需要直接忽略
					return (el.nodeType == 1 && ['STYLE', 'SCRIPT'].indexOf(el.nodeName) == -1); 
				});
		}
		
	// end of private members
	
	TabView.prototype = {
		/**
		 * private
		 * 从mackup中解析出this.triggers和this.panels
		 * 子类可以覆盖此方法，以适用不同的mackup
		 */
		parseMackup: function() {
			// 解析mackup的顺序是：
			// 1. 根据navBarCls和panelsWrapperCls来获取；
			// 2. container下面包含navBar和panelsWrapper两个子元素，navBar可以
			//    由navBarCls指定，或者自动获取第一个ul/ol子元素作为navBar.
			// 原则：给与适当的灵活性
			
			var cfg = this.config;
			
			// 1. 根据Cls来获取
			// 适用情况：container下面不仅包含navBar和panelsWrapper，还包含其它一些
			// 元素（比如纯装饰性的透明层等）
			var navBar = D.getFirstChildBy(this.container, function(child) {
				return D.hasClass(child, cfg.navBarCls);
			});
			var panelsWrapper = D.getFirstChildBy(this.container, function(child) {
				return D.hasClass(child, cfg.panelsWrapperCls);
			});
			
			// 上面的方法没获取全
			if(!navBar || !panelsWrapper) {
				// 2. container下面只包含两个子元素时
				var elems = D.getChildren(this.container);
				if(elems.length == 2) {
					// 2.1 首先根据navBarCls来获取导航条
					for(var i = 0; i < 2; ++i) {
						if(D.hasClass(elems[i], this.config.navBarCls)) {
							panelsWrapper = elems[1 - i];
							navBar = elems[i];
							break;
						}
					}
					
					// 2.2 将子节点中的第一个ul/ol作为导航条
					if(!navBar) {
						for(var i = 0; i < 2; ++i) {
							var nodeName = elems[i].nodeName.toUpperCase();
							if(nodeName == 'UL' || nodeName == 'OL') {
								panelsWrapper = elems[1 - i];
								navBar = elems[i];
								break;
							}
						}
					}	
				}
			}
			
			// 初始化未成功，抛异常
			if(!navBar || !panelsWrapper) {
				throw new Error('Can not get navBar and panelsWrapper.');
			}
			
			// 成功解析，获取triggers和panels
			this.panels = getHtmlElementsChildren(panelsWrapper);
			this.triggers = getHtmlElementsChildren(navBar);
			
		},
		
		/**
		 * 设置激活项
		 */
		setActiveItem: function(index) {
			//Unicorn.log('call setActiveItem of TabView', 'info', 'TabView');
			var len = this.panels.length;
			if(len < 1) return;
			
			// 值非法时，默认选中第一项
			if(!(index in this.panels)) index = 0;
			
			var cfg = this.config;
			var activePanel = this.panels[cfg.activeIndex]; // 当前激活项
			var newActivePanel = this.panels[index]; // 即将激活的项

			// 如果beforeSwitch返回false，终止进行
			if(this.events['beforeSwitch'].fire(this) === false) {
				return;
			}
			
			// 切换导航条上的样式
			D.removeClass(activePanel.trigger, cfg.activeNavItemCls);
			D.addClass(newActivePanel.trigger, cfg.activeNavItemCls);
			
			// 更新内容
			this.switchContent(activePanel, newActivePanel, index);
		},
		
		/**
		 * private
		 * 子类中可以覆盖此方法，以实现不同的切换效果
		 * 这里默认实现的是最简单的一种切换
		 */
		switchContent: function(activePanel, newActivePanel, index) {
			var cfg = this.config;
			
			// 最简单的隐藏/显示效果
			D.setStyle(activePanel, 'display', 'none');
			D.setStyle(newActivePanel, 'display', '');
			
			// 更新activeIndex
			cfg.activeIndex = index;
			
			// 触发切换后事件
			this.events['onSwitch'].fire(this);			
			
			// location响应变化，保证刷新时保留当前激活项
			if(cfg.parseHash) {
				var url = location.href.replace(location.hash, '');
				location.replace(url + this.panels[index].hash);
			}
		}
	};
	
	return TabView;
}();

// 增加decorate静态方法
Unicorn.widget.TabView.decorate = function(container, config) {
	return new Unicorn.widget.TabView(container, config);
};

/**
 * @class Unicorn.widget.SlideView
 * creator: lifesinger@gmail.com
 * 想法：SlideView可以看作是TabView的变体，继承TabView，稍作变化即可
 */

/**
 * super: TabView
 * members:
 *   see TabView
 *   this.panelsWrapper (有些效果的实现需要panelsWrapper，故增加此属性)
 * private members:
 *   this.effectType
 *   this.panelHeight, this.panelWidth
 *   this.slideAnim, this.autoPlayTimer, this.autoPlayIsPaused
 */

Unicorn.widget.SlideView = function() {
	var L  = Unicorn.lang,
		U  = Unicorn.util,
		D  = U.Dom,
		E  = U.Event,
		helper = Unicorn.widget.WidgetHelper;

	// SlideView的默认配置
	// 和TabView相同的部分请参见TabView.source.js
	var defConfig = {
			containerCls: 'slide-container',
			navBarCls: 'slide-nav',
			panelsWrapperCls: 'slide-wrapper',
			
			eventType: 'mouse', // 默认为'mouse'
			autoPlay: true, // 是否自动播放
			autoPlayInterval: 3, // 自动播放间隔时间，默认3秒
			
			pauseOnMouseOver: true,  // 当eventType为mouse时，当鼠标悬停在slide上时，是否暂停自动播放
			
			effect: 'none', // 'scrollx', 'scrolly', 'fade', 'custom'
			animDuration: 0.5, // 开启切换效果时，切换的时长
			animEasing: Unicorn.util.Easing.easeOutStrong, // easing method
			animAttributes: {}, // effect为'custom'时，通过animAttributes设定自定义动画属性 // TODO
			
			panelSize: {} // 卡盘panel的宽高。一般不需要设定此值。
						  // 只有当无法正确获取高宽时（比如TabView的隐藏Tab中含有SlideView时），才需要设定
						  // 因为父级元素中有display: none时，无法获取到offsetWidth, offsetHeight
		};
	
	var SlideView = function() {
		var cfgs = helper.parseArguments(arguments);
		
		// 获取默认配置
		var config = L.applyIf(cfgs[0] || {}, defConfig);
		 
		// chain ctor
		this.constructor.superclass.constructor.call(this, config);
		
		init.call(this);
	};
	
	function init() {
		if(!this.container) return;
		var cfg = this.config;
		
		// 获取panelsWrapper
		this.panelsWrapper = this.panels[0].parentNode; // 对于SlideView来说，mackup是固定模式的
		
		// 根据effectType，调整初始状态
		adjustStyle.call(this);
		
		// 处理pauseOnMouseOver
		if(cfg.eventType == 'mouse' && cfg.autoPlay && cfg.pauseOnMouseOver) {
			pauseOnMouseOver.call(this);
		}
		
		// 设置自动播放
		if(cfg.autoPlay) {
			setAutoPlay.call(this);
		}
	}
	
	// private members
		/**
		 * 根据effectType，调整初始状态
		 */
		function adjustStyle() {
			var cfg = this.config;
			
			this.panelHeight = cfg.panelSize.height || this.panels[0].offsetHeight; // 所有panel的尺寸应该相同
			this.panelWidth = cfg.panelSize.width || this.panels[0].offsetWidth;
			// 最好指定第一张图片的width和height，因为Safari下，图片未加载时，读取的offsetHeight等值会不对
			//Unicorn.log('panelHeight = ' + this.panelHeight, 'info', 'SlideView');
			//Unicorn.log('panelWidth = ' + this.panelWidth, 'info', 'SlideView');
			
			this.effectType = cfg.effect.toLowerCase();
			if(['scrolly', 'scrollx'].indexOf(this.effectType) > -1) {
				// 这些特效需要将panels都显示出来
				this.panels.forEach(function(panel) { D.setStyle(panel, 'display', ''); });
				
				// 设置最大高宽，以保证最后不会出现空白，以及float:left时，能水平排布
				// 这里按理说应该交给css处理，但为了避免更换图片时，修改的麻烦，所以保留这里的自动设置还是有必要的
				if(this.effectType == 'scrollx') {
					D.setStyle(this.panelsWrapper, 'width', this.panelWidth * this.panels.length + 'px');
				} else {
					D.setStyle(this.panelsWrapper, 'height', this.panelHeight * this.panels.length + 'px');
				}
			} else if(this.effectType == 'fade') {
				// 让所有panel初始时都处于透明状态
				this.panels.forEach(function(panel) {
					D.setStyle(panel, 'display', '');
					setOpacity(panel, 0);
				});
				
				// 显示激活的panel
				setOpacity(this.panels[cfg.activeIndex], 1);
			}
		}
		
		/**
		 * 鼠标悬停时，停止自动播放
		 */
		function pauseOnMouseOver() {
			navBar = this.triggers[0].parentNode; // navBar仅这里用到，就不弄个this.navBar了，遵从属性最少原理
			
			E.on([this.panelsWrapper, navBar], 'mouseover', function() {
				this.autoPlayIsPaused = true;
			}, null, this);
			
			E.on([this.panelsWrapper, navBar], 'mouseout', function() {
				this.autoPlayIsPaused = false;
			}, null, this);
		}
		
		/**
		 * 处理自动播放
		 */
		function setAutoPlay() {
			var cfg = this.config;
			
			var fn = function() {				
				var n = cfg.activeIndex;
				n = (n < this.panels.length - 1) ? n + 1 : 0;
				
				this.setActiveItem(n, true);
			};
			
			this.autoPlayTimer = L.later(cfg.autoPlayInterval * 1000, this, fn, null, true);
		}
		
		/**
		 * 设置Panel的透明度，val只能为0或1
		 */
		function setOpacity(panel, val) {
			D.setStyle(panel, 'opacity', val);
			D.setStyle(panel, 'filter', 'alpha(opacity=' + val * 100 + ')');
		}
		
		/**
		 * 动画完成时的事件
		 */
		function animCompleteHandler(activePanel, newActivePanel, index) {
			// 触发切换后事件
			this.events['onSwitch'].fire(activePanel, newActivePanel);
			
			// free
			this.slideAnim = null;
		}
	// end of private members
	
	Unicorn.extend(SlideView, Unicorn.widget.TabView, {
		/**
		 * 激活指定的Slide
		 */
		setActiveItem: function(index, auto) {
			if(auto) { // 自动播放碰到暂停或动画尚未结束时，推迟到下一次
				if(this.autoPlayIsPaused) return; // 暂停状态
				if(this.slideAnim && this.slideAnim.isAnimated()) return; // 动画尚未结束
			} else { // 用户触发的事件
				if(this.slideAnim && this.slideAnim.isAnimated()) { // 立刻停止当前动画
					this.slideAnim.stop();
				}
			}
			
			//Unicorn.log('call setActiveItem of SlideView', 'info', 'SlideView');
			this.constructor.superclass.setActiveItem.call(this, index);
		},
		
		// private
		switchContent: function(activePanel, newActivePanel, index) {
			var cfg = this.config;
			
			if(['scrolly', 'scrollx'].indexOf(this.effectType) > -1) { // 垂直/水平滚动效果
				var isX = (this.effectType == 'scrollx');
				
				var diff = (isX ? this.panelWidth : this.panelHeight) * index;
				var attributes = { };
				attributes[isX ? 'left' : 'top'] = { to: -diff };
				
				this.slideAnim = new U.Anim(this.panelsWrapper, attributes, cfg.animDuration, cfg.animEasing);
				this.slideAnim.onComplete.subscribe(function(){
					animCompleteHandler.call(this, activePanel, newActivePanel, index);
				}, this, true);
				
				// 更新activeIndex
				cfg.activeIndex = index;
				this.slideAnim.animate();
					
			} else if(this.effectType == 'fade') { // 淡隐淡出效果
				// 设置z-index
				D.setStyle(newActivePanel, 'z-index', '8');
				D.setStyle(activePanel, 'z-index', '9');
				
				// 首先显示下一张
				setOpacity(newActivePanel, 1);
					
				this.slideAnim = new U.Anim(activePanel, { opacity: { to: 0 } }, cfg.animDuration, cfg.animEasing);
				this.slideAnim.onComplete.subscribe(function(){
					animCompleteHandler.call(this, activePanel, newActivePanel, index);
				}, this, true);
				
				// 更新activeIndex
				cfg.activeIndex = index;
				this.slideAnim.animate();
				
			} else { // 最普通的显示/隐藏效果
				this.constructor.superclass.switchContent.call(this, activePanel, newActivePanel, index);
			}
		}
	});
	
	return SlideView;
}();

// 增加decorate静态方法
Unicorn.widget.SlideView.decorate = function(container, config) {
	return new Unicorn.widget.SlideView(container, config);
};

/**
 * @class Unicorn.widget.LiquidView
 * creator: lifesinger@gmail.com
 * 想法：LiquidView可以看作是TabView的变体，继承TabView，稍作变化即可
 */

/**
 * super: TabView
 * members:
 *   see TabView
 * private members:
 *   this.effectType
 *   this.effectAnims
 */

Unicorn.widget.LiquidView = function() {
	var L  = Unicorn.lang,
		U  = Unicorn.util,
		D  = U.Dom,
		E  = U.Event,
		helper = Unicorn.widget.WidgetHelper;

	// LiquidView的默认配置
	// 和TabView相同的部分请参见TabView.source.js
	var defConfig = {		
			eventType: 'mouse', // 默认为'mouse'，且只能为'mouse'，用户不能更改
			disableTriggerClick: false, // 默认可以点击，因为一般都是一个图片链接
			resetOnMouseOut: true, // 鼠标移出container时，是否还原到初始状态
			activeIndex: -1, // 默认没有激活任何panels，取-1
			
			effect: 'scrollx', // 'scrollx', 'scrolly'
			animDuration: 0.5, // 开启切换效果时，切换的时长
			animEasing: Unicorn.util.Easing.easeOutStrong,
			
			// scrollx
			maxWdith: '', // px
			minWidth: '',
			normalWidth: '',
			
			// scrolly
			maxHeight: '', // px
			minHeight: '',
			noramlHeight: ''
		};
	
	var LiquidView = function() {
		var cfgs = helper.parseArguments(arguments);
		
		// 获取默认配置
		var config = L.applyIf(cfgs[0] || {}, defConfig);
		
		// 设置特定属性
		config.eventType = 'mouse';
		
		// chain ctor
		this.constructor.superclass.constructor.call(this, config);
		
		init.call(this);
	};
	
	function init() {
		if(!this.container) return;
		var cfg = this.config;
		
		this.effectType = cfg.effect.toLowerCase();
		this.effectAnims = [];
		
		// 取消父类里对panel的隐藏
		this.panels.forEach(function(panel) { D.setStyle(panel, 'display', ''); });
		
		// 添加重置事件
		if(cfg.resetOnMouseOut === true) {
			E.on(this.container, 'mouseout', function(e) {
				// 移动到内部元素上时，也会触发container的mouseout
				// 这显然不是我们想要的，应该检测出来不执行
				if(D.isAncestor(this.container, E.getRelatedTarget(e))) return;
				
				// 快速mouse over out时，因为触发有delay，所以over时可能还未触发动画，这时
				// mouse out时，无需reset
				if(cfg.activeIndex != -1) {
					//Unicorn.log('this.setActiveItem(-1) fired.', 'info', 'LiquidView');
					this.setActiveItem(-1);
				}
				
			}, this, true);
		}
	}
	
	// private members
		/**
		 * 执行动画
		 * @param index 表示当前要展开的panel. 当index为-1时，表示reset
		 */
		function doAnimate(index) {
			var cfg = this.config;
			
			if(['scrolly', 'scrollx'].indexOf(this.effectType) > -1) { // 垂直/水平滚动效果
				var isX = (this.effectType == 'scrollx');
				var prop = isX ? 'width' : 'height';
				
				var toMax = {};
				var toMin = {};
				var toNormal = {};
				toMax[prop] = { to: (isX ? cfg.maxWidth : cfg.maxHeight) };
				toMin[prop] = { to: (isX ? cfg.minWidth : cfg.minHeight) };
				toNormal[prop] = { to: (isX ? cfg.normalWidth : cfg.normalHeight) };
				
				this.panels.forEach(function(panel, j) {
					var attributes = toMin;
					if(index == -1) attributes = toNormal;
					else if(index == j) attributes = toMax;
					
					if(index != -1 && cfg.activeIndex > -1) { // 已经有一个处于展开状态时
						if(j != cfg.activeIndex && j != index) { // 仅需要两个panel执行动画即可
							return;
						}
					}
					
					this.effectAnims[j] = new U.Anim(panel, attributes, cfg.animDuration, cfg.animEasing);
					this.effectAnims[j].onComplete.subscribe(function(){
						// 触发切换后事件
						this.events['onSwitch'].fire(this);
						
						// free
						this.effectAnims[j] = null;
					}, this, true);
				}, this);
				
				// 更新activeIndex
				cfg.activeIndex = index;
				this.effectAnims.forEach(function(anim) { if(anim) anim.animate(); });
					
			} else { // 无动画效果
				throw new Error('Do not support this effectType: ' + this.effectType);
			}
		}
	// end of private members
	
	Unicorn.extend(LiquidView, Unicorn.widget.TabView, {
		/**
		 * private
		 * 从mackup中解析出this.triggers和this.panels
		 */
		parseMackup: function() {
			// triggers是container下面的子元素
			this.triggers = D.getChildren(this.container);
			
			// panel是trigger下面的子元素
			this.panels = [];
			this.triggers.forEach(function(trigger) {
				this.panels.push(D.getFirstChild(trigger));
			}, this);
		},
		
		/**
		 * 激活指定项
		 */
		setActiveItem: function(index) {
			if(this.effectAnims) {
				for(var i = 0, len = this.effectAnims.length; i < len; ++i) {
					var anim = this.effectAnims[i];
					if(anim && anim.isAnimated()) {
						anim.stop(true); // 注意参数，一定要结束时跳到最后一帧，否则容易错位
					}
				}
			}
			
			// 如果beforeSwitch返回false，终止进行
			if(this.events['beforeSwitch'].fire(this) === false) return;
			
			//Unicorn.log('call setActiveItem of LiquidView', 'info', 'LiquidView');
			var cfg = this.config;
			var activePanel = this.panels[cfg.activeIndex]; // 当前激活项
			var newActivePanel = this.panels[index]; // 即将激活的项

			// 切换导航条上的样式
			if(activePanel) D.removeClass(activePanel.trigger, cfg.activeNavItemCls);
			if(newActivePanel) D.addClass(newActivePanel.trigger, cfg.activeNavItemCls);
			
			// 执行动画
			doAnimate.call(this, index);
		}
	});
	
	return LiquidView;
}();

// 增加decorate静态方法
Unicorn.widget.LiquidView.decorate = function(container, config) {
	return new Unicorn.widget.LiquidView(container, config);
};

/**
 * for TAOBAO
 */

// alias
TAOBAO = Unicorn;

